#include "common.h"
#include <pthread.h>
#include <signal.h>
#include <math.h>

ServerConfig_t server_config;
int WINNER = 0;

char buf[MAX_MESSAGE];

void enqueue(Fleet_t *fleet)
{
    if(server_config.fleets == NULL) {
        // insert fleet in empty queue
        server_config.fleets = fleet;
        return;
    }
    if(fleet->eta < server_config.fleets->eta) {
        // enqueue in the first position
        fleet->next = server_config.fleets;
        server_config.fleets = fleet;
        return;
    }
    Fleet_t *top = server_config.fleets;
    while((top->next != NULL) && (fleet->eta > top->next->eta)) {
        top = top->next;
    }
    // insert after top
    fleet->next = top->next;
    top->next = fleet;
}

Fleet_t* dequeue(void)
{
    Fleet_t *top = NULL;
    Fleet_t *last = server_config.fleets;
    while(last != NULL) {
        if(last->eta > 0) {
            break;
        }
        server_config.fleets = last->next;
        last->next = NULL;
        if(top == NULL) {
            top = last;
        } else {
            last->next = top->next;
            top->next = last;
        }
        last = server_config.fleets;
    }
    return top;
}

void eventLoop(int sig)
{
    // update planet soliders
    int i;
    int game_ends = 1;
    int fleet_owner = -1, planet_owner = -1;
    Planet_t *planet;
    for(i = 0; i < server_config.PID; i++) {
        planet = &server_config.planets[i];
        planet->soliders += (int) (planet->capacity / (double) server_config.min_capacity);
        // do not exceed planet's capacity
        if(planet->soliders > planet->capacity) {
            planet->soliders = planet->capacity;
        }
        if(planet_owner == -1) {
            planet_owner = planet->uid;
        } else if(planet_owner != planet->uid) {
            game_ends = 0;
        }
    }
    Fleet_t *top = dequeue();
    Fleet_t *last;
    while(top != NULL) {
        // update planet's status
        printf("%i[uid %i]->%i with %i ships\n", top->from, top->uid, top->to, top->amount);
        planet = &server_config.planets[top->to];
        if(planet->uid == top->uid) {
            planet->soliders += top->amount;
        } else {
            planet->soliders -= top->amount;
        }
        if(planet->soliders < 0) {
            printf("[%i] captured planet %i from [%i]\n", top->uid, top->to, planet->uid);
            planet->uid = top->uid;
            planet->soliders *= -1;
        }
        last = top;
        top = last->next;
        free(last);
    }
    // move fleets 1s towards the goal
    top = server_config.fleets;
    while(top != NULL) {
        top->eta -= 1;
        if(fleet_owner == -1) {
            fleet_owner = top->uid;
        } else if(fleet_owner != top->uid) {
            game_ends = 0;
        }
        top = top->next;
    }
    if((planet_owner != -1) && (fleet_owner != -1) && (planet_owner != fleet_owner)) {
        game_ends = 0;
    }
    if(game_ends) {
        //black magic
        printf("game ends! player %i wins.\n", planet_owner);
        WINNER = planet_owner;
    } else {
        // reinstall handler
        alarm(1);
        signal(SIGALRM, eventLoop);
    }
}

void timeOutHandler(int sig) {
    alarm(1);
    signal(SIGALRM, SIG_IGN);
    if(server_config.start_timeout > 1) {
        printf("time left: %is...\n", server_config.start_timeout);
        signal(SIGALRM, timeOutHandler);
    } else {
        printf("game starting.\n");
        // make unowned planets neutral
        int i;
        Planet_t *planet;
        for(i = 0; i < server_config.planet_count; i++) {
            planet = &server_config.planets[i];
            if(planet->uid > server_config.UID) {
                planet->uid = 0;
            }
        }
        signal(SIGALRM, eventLoop);
    }
    server_config.start_timeout -= 1;
}

// J IP NAME
// J playerid starttime
void handleJoinCommand(int fd, char* command, int len)
{
    int UID = 0;
    if((server_config.UID < server_config.max_players) && (server_config.start_timeout > 0)){
        UID = server_config.UID++;
        Player_t new_player;
        new_player.name = malloc(strlen(&command[2]) + 1);
        memcpy(new_player.name, &command[2], len-2);
        server_config.players[UID] = new_player;
        new_player.uid = ++UID;
    }
    sprintf(buf, "J %i %i", UID, server_config.start_timeout);
    sendData(fd, (char *) buf, strlen(buf) + 1);
}
// M
// M planet_count planetid_x_y_ownerid_capacity_soldiers, ...
void handleMapCommand(int fd, char* command,  int len)
{
    int i;
    int buf_len = sprintf(buf, "M %i", server_config.PID);
    Planet_t *planet;
    for(i = 0; i < server_config.PID; i++) {
        planet = &server_config.planets[i];
        buf_len += sprintf(buf + buf_len, " %i_%i_%i_%i_%i_%i",
            planet->pid, planet->x, planet->y, planet->uid, planet->capacity,
            planet->soliders);
    };
    if(WINNER != 0) {
        sprintf(buf, "%s %i", buf, WINNER);
    }
    sendData(fd, (char *) buf, strlen(buf) + 1);
}

// S FROM_TO_AMOUNT
// S SentCount ArrivalTime
void handleSendCommand(int fd, char* command,int len)
{
    int from, to, amount, eta;
    if(sscanf(command, "S %i %i %i", &from, &to, &amount) != 3) {
        perror("wrong 'send' parameters");
        sendData(fd, "S 0 0", 6);
        return;
    }
    Planet_t *planet_from = &server_config.planets[from];
    Planet_t *planet_to = &server_config.planets[to];
    // check that requested force is present on the planet
    if(planet_from->soliders < amount) {
        amount = planet_from->soliders;
    }
    // calculate distance / time of arrival
    int dx = planet_from->x - planet_to->x;
    int dy = planet_from->y - planet_to->y;
    eta = sqrt((double) dx * dx + dy * dy) / server_config.speed;
    // fill in information
    planet_from->soliders -= amount;
    Fleet_t *fleet = calloc(1, sizeof(Fleet_t));
    fleet->from = from;
    fleet->to = to;
    fleet->amount = amount;
    fleet->eta = eta;
    fleet->uid = planet_from->uid;
    enqueue(fleet);
    sprintf(buf, "S %i %i", amount, eta);
    sendData(fd, (char *) buf, strlen(buf) + 1);
}

// U
// U Count id_username...
void handleUserListCommand(int fd, char* command, int len)
{
    int i;
    sprintf(buf, "U %i", server_config.UID);
    for(i = 0; i < server_config.UID; i++) {
        sprintf(buf, "%s %d_%s", buf, i + 1, server_config.players[i].name);
    };
    sendData(fd, (char *) buf, strlen(buf) + 1);
}

// A userid
// A count planetid_number_from_attackerid_timeuntilreaches
void handleAttackCommand(int fd, char* command, int len)
{
    int count = 0;
    int buf_len = 4;
    strcpy(buf, "A %i");
    Fleet_t *top = server_config.fleets;
    while(top != NULL) {
        count++;
        buf_len += sprintf(buf + buf_len, " %i_%i_%i_%i_%i",
            top->to, top->amount, top->from, top->uid, top->eta);
        top = top->next;
    }
    sprintf(buf, buf, count);
    sendData(fd, buf, strlen(buf) + 1);
}

void handleData(int fd, char* command, int len) {
    char c;
    if(len>0){
        c=command[0];
    }
    printf("received command: '%s'\n", command); // XXX debug
    switch(c){
    case 'J':
        handleJoinCommand(fd, command, len);
        break;
    case 'M':
        handleMapCommand(fd, command, len);
        break;
    case 'S':
        handleSendCommand(fd, command, len);
        break;
    case 'U':
        handleUserListCommand(fd, command, len);
        break;
    case 'A':
        handleAttackCommand(fd, command, len);
        break;
    default:
        fprintf(stderr, "handleData: unknown command %c!", c);
    }
}

// izdrukā klienta informāciju
void printClient(int fd) {
    struct sockaddr_in addr;
    socklen_t addrLen = sizeof(addr);

    if (getpeername(fd, (struct sockaddr *) &addr, &addrLen) == -1) {
        perror("getpeername");
        return;
    }

    printf("%s:%d connected via TCP\n",
        inet_ntoa(addr.sin_addr),
        ntohs(addr.sin_port));
}


// TCP klienta datu apstrādes funkcija (tiek izsaukta jaunā pavedienā)
void *clientHandler(void *param)
{
    long sock_fd = (long) param;
    char command[MAX_MESSAGE];
    for (;;) {

        int len = recv(sock_fd, command, sizeof(command), 0);
        if(len == 0) {
            printf("client disconnected\n");
            return NULL;
        } else if(len == -1) {
            perror("recv failed");
        }
        if(len != (strlen(command) + 1)) {
            fprintf(stderr, "TCP data length mismatch (%i expected, got %i)\n",
                (int) len,(int) (strlen(command) + 1));
            return NULL;
        }
        handleData(sock_fd, command, len);
    }
}

int readConfig(char *config_name)
{
    FILE *config = fopen(config_name, "r");
    char input[MAX_LEN];
    int x, y, uid, cap, sold;
    if(config == NULL) {
        perror("Error opening config file");
        return -1;
    }
    while(1) {
        fgets(input, MAX_LEN, config);
       // printf("%s", input);
        if (feof(config)) {
            break;
        }
        if(strlen(input) > 0) {
            // fgets() copies ending newlines
            input[strlen(input) - 1] = '\0';
        }
        if(sscanf(input, "PLAYERS %i", &x) == 1) {
            server_config.max_players = x;
            server_config.players = (
                malloc(sizeof(Player_t) * server_config.max_players));
        } else if(sscanf(input, "SPEED %i", &x) == 1) {
            server_config.speed = x;
        } else if(sscanf(input, "START_TIME %i", &x) == 1) {
            server_config.start_timeout = x;
        } else if(sscanf(input, "MIN_CAPACITY %i", &x) == 1) {
            server_config.min_capacity = x;
        } else if(sscanf(input, "MAX_CAPACITY %i", &x) == 1) {
            server_config.max_capacity = x;
        } else if(sscanf(input, "PLANET_COUNT %i", &x) == 1) {
            server_config.planet_count = x;
            server_config.planets = (
                malloc(sizeof(Planet_t) * server_config.planet_count));
        } else if(sscanf(input, "PLANET %i %i %i %i %i", &x, &y, &uid, &cap, &sold) == 5) {
            Planet_t p;
            p.pid = server_config.PID++;
            p.x = x;
            p.y = y;
            p.uid = uid;
            p.capacity = cap;
            p.soliders = sold;
            server_config.planets[server_config.PID - 1] = p;
        }
    }
    return 0;
}

int main(int argc, char *argv[])
{
    char *config_name = argc > 1 ? argv[1] : "config.ini";
    // read game config
    readConfig(config_name);
    printf("Max players: %d\n", server_config.max_players);
    printf("Speed: %d\n", server_config.speed);
    printf("Start_time: %d\n", server_config.start_timeout);
    printf("Min capacity: %d\n", server_config.min_capacity);
    printf("Max capacity: %d\n", server_config.max_capacity);

    printf("Planet count: %d\n", server_config.planet_count);
    // initialize TCP socket
    int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (sock_fd < 0) {
        perror("could not create TCP socket");
        return -1;
    }
    printf("socket created\n");

    // initialize socked address
    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);

    if (bind(sock_fd, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0) {
        perror("TCP bind failed");
        return -1;
    }
    printf("socked bound\n");

    // listen incoming connections
    if (listen(sock_fd, server_config.max_players) == -1) {
        perror("listen");
        return -1;
    }
    printf("waiting for connections...\n");

    // start countdown
    alarm(1);
    signal(SIGALRM, timeOutHandler);

    // cikls, kurā uz TCP soketa tiek akceptēti jauni klienti
    for (;;) {
        struct sockaddr_in client_addr;
        socklen_t addr_size = sizeof(client_addr);
        long c_sock_fd = accept(sock_fd, (struct sockaddr *) &client_addr, &addr_size);
        if (c_sock_fd < 0) {
            perror("client connection denied");
            return -1;
        }
        printf("client connection accepted\n");

        // klients pievienojies, izdrukāju
        printClient(c_sock_fd);

        // izveidojam savu pavedienu šim klientam
        pthread_t clientThread;
        pthread_create(&clientThread, NULL, clientHandler, (void *) c_sock_fd);
    }
}
